package models

import (
	"errors"
	"go_pure_admin_api/dto"
	"go_pure_admin_api/global"
	"gorm.io/gorm"
)

// Departments 部门表
type Departments struct {
	gorm.Model
	DeptName string        `gorm:"comment:部门名称;size:50" json:"name" validate:"required" form:"name"`
	Sort     int           `gorm:"comment:排序;default:0" json:"sort" form:"sort"`
	Status   int8          `gorm:"comment:状态 0:禁用 1:启用;default:1" json:"status" form:"status"`
	Remark   string        `gorm:"comment:备注;size:500" json:"remark" form:"remark"`
	ParentID *uint         `gorm:"comment:父级ID;default:NULL;constraint:OnDelete:CASCADE,OnUpdate:RESTRICT" json:"parentId" form:"parentId"`
	Parent   *Departments  `gorm:"foreignKey:ParentID;constraint:OnDelete:CASCADE,OnUpdate:RESTRICT" json:"parent"`
	Children []Departments `gorm:"foreignKey:ParentID" json:"children"`
	/*
		这里有个问题。因为部门表和用户表相互引用导致gorm创建表时候会报错。解决方法先注释 Leader 和 LeaderID 这两行字段，然后运行一次项目，
		创建表后，再取消注释 Leader 和 LeaderID 这两行字段，然后运行一次项目，就会创建成功。
	*/
	Leader   *Users  `gorm:"foreignKey:LeaderID;constraint:OnDelete:SET NULL" json:"leader"`
	LeaderID uint    `gorm:"comment:部门负责人ID;constraint:OnDelete:SET NULL" json:"leader_id"`
	Users    []Users `gorm:"foreignKey:DepartmentID" json:"users"`
}

func init() {
	err := global.DB.AutoMigrate(&Departments{})
	if err != nil {
		global.Logger.Errorf("自动迁移部门表错误: %v", err)
		return
	}
	// 强制创建级联约束
	global.DB.Exec("ALTER TABLE departments DROP FOREIGN KEY fk_departments_children")
	global.DB.Exec(`
        ALTER TABLE departments 
        ADD CONSTRAINT fk_departments_children 
        FOREIGN KEY (parent_id) REFERENCES departments(id) 
        ON DELETE CASCADE
    `)
}
func GetAllDepartmentsList() (departments []Departments, err error) {
	err = global.DB.Preload("Leader").Order("sort ASC").Find(&departments).Error
	return
}

func (d *Departments) CreateDepartment(data dto.CreateDeptDto) (err error) {
	d.DeptName = data.Name
	d.Status = data.Status
	d.Sort = data.Sort
	if data.ParentID != 0 {
		d.ParentID = &data.ParentID
	} else {
		d.ParentID = nil
	}
	d.LeaderID = data.LeaderID
	d.Remark = data.Remark
	err = global.DB.Create(d).Error
	return
}

func (d *Departments) UpdateDepartment(data dto.UpdateDeptDto) (err error) {
	// 先查询当前部门
	err = global.DB.First(d, data.ID).Error
	if err != nil {
		return err
	}

	// 如果要设置父部门，需要进行循环引用检查
	if data.ParentID != 0 {
		// 检查是否设置为自己
		if data.ParentID == data.ID {
			return errors.New("不能将部门的父级设置为自己")
		}

		// 检查是否设置为自己的子部门（避免循环引用）
		isChild, err := d.isChildDepartment(data.ParentID, data.ID)
		if err != nil {
			return err
		}
		if isChild {
			return errors.New("不能将部门的父级设置为自己的子部门，这会造成循环引用")
		}

		// 检查父部门是否存在
		var parentDept Departments
		err = global.DB.First(&parentDept, data.ParentID).Error
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errors.New("指定的父部门不存在")
			}
			return err
		}
	}

	// 验证通过，开始更新
	d.DeptName = data.Name
	if data.ParentID != 0 {
		d.ParentID = &data.ParentID
	} else {
		d.ParentID = nil
	}
	d.LeaderID = data.LeaderID
	d.Remark = data.Remark
	d.Status = data.Status

	return global.DB.Save(d).Error
}

// 辅助方法：检查指定部门是否是当前部门的子部门
func (d *Departments) isChildDepartment(targetParentID, currentDeptID uint) (bool, error) {
	// 获取当前部门的所有子部门ID
	childIDs, err := d.getAllChildDepartmentIDsWithCTE(currentDeptID)
	if err != nil {
		return false, err
	}
	// 检查目标父部门是否在子部门列表中
	for _, childID := range childIDs {
		if childID == targetParentID {
			return true, nil
		}
	}
	return false, nil
}

// getAllChildDepartmentIDsWithCTE 使用CTE（Common Table Expression）一次性查询所有子部门
func (d *Departments) getAllChildDepartmentIDsWithCTE(parentID uint) ([]uint, error) {
	var childIDs []uint

	// 使用递归CTE查询所有子部门
	query := `
		WITH RECURSIVE dept_tree AS (
			-- 基础查询：直接子部门
			SELECT id FROM departments WHERE parent_id = ?
			UNION ALL
			-- 递归查询：子部门的子部门
			SELECT d.id 
			FROM departments d 
			INNER JOIN dept_tree dt ON d.parent_id = dt.id
		)
		SELECT id FROM dept_tree
	`

	err := global.DB.Raw(query, parentID).Scan(&childIDs).Error
	return childIDs, err
}

func (d *Departments) DeleteDeptByID(deptID uint) error {
	return global.DB.Unscoped().Delete(d, deptID).Error
}
